/**
 * Copyright (C) 2016-2019 DSpot Sp. z o.o
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dspot.declex.adapter;

import com.dspot.declex.adapter.plugin.BaseClassPlugin;
import com.dspot.declex.annotation.UseModel;
import com.dspot.declex.handler.PopulateHandler;
import com.dspot.declex.holder.ViewsHolder;
import com.dspot.declex.holder.ViewsHolder.ICreateViewListener;
import com.dspot.declex.holder.ViewsHolder.IWriteInBloc;
import com.dspot.declex.holder.ViewsHolder.IdInfoHolder;
import com.dspot.declex.holder.view_listener.ViewListenerHolder;
import com.dspot.declex.util.DeclexConstant;
import com.dspot.declex.util.TypeUtils;
import com.helger.jcodemodel.AbstractJClass;
import com.helger.jcodemodel.AbstractJType;
import com.helger.jcodemodel.IJExpression;
import com.helger.jcodemodel.JBlock;
import com.helger.jcodemodel.JClassAlreadyExistsException;
import com.helger.jcodemodel.JConditional;
import com.helger.jcodemodel.JDefinedClass;
import com.helger.jcodemodel.JFieldRef;
import com.helger.jcodemodel.JMethod;
import com.helger.jcodemodel.JMod;
import com.helger.jcodemodel.JVar;

import org.ohosannotations.helper.CanonicalNameConstants;
import org.ohosannotations.holder.EComponentHolder;
import org.ohosannotations.logger.Logger;
import org.ohosannotations.logger.LoggerFactory;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

import static com.dspot.declex.api.util.FormatsUtils.fieldToGetter;
import static com.helger.jcodemodel.JExpr._new;
import static com.helger.jcodemodel.JExpr._null;
import static com.helger.jcodemodel.JExpr._this;
import static com.helger.jcodemodel.JExpr.cast;
import static com.helger.jcodemodel.JExpr.invoke;
import static com.helger.jcodemodel.JExpr.ref;
import static org.ohosannotations.helper.ModelConstants.generationSuffix;

/**
 * 查看适配器填充器
 *
 * @author ohos
 * @date 2021/07/27
 */
public class ViewAdapterPopulator extends BaseClassPlugin {
    /**
     * 日志记录器
     */
    protected static final Logger LOGGER = LoggerFactory.getLogger(ViewAdapterPopulator.class);
    /**
     * 处理程序
     */
    private PopulateHandler handler;
    /**
     * 观点持有者
     */
    private ViewsHolder viewsHolder;
    /**
     * 适配器类名
     */
    private String adapterClassName;
    /**
     * 模型类名称
     */
    private String modelClassName;
    /**
     * 字段名
     */
    private String fieldName;

    /**
     * 查看适配器填充器
     *
     * @param handler 处理程序
     * @param fieldName 字段名
     * @param adapterClassName 适配器类名
     * @param modelClassName 模型类名称
     * @param viewsHolder 观点持有者
     */
    public ViewAdapterPopulator(PopulateHandler handler, String fieldName, String adapterClassName, String modelClassName,
                                ViewsHolder viewsHolder) {
        super(viewsHolder.environment());
        this.handler = handler;
        this.fieldName = fieldName;
        this.adapterClassName = adapterClassName;
        this.modelClassName = modelClassName;
        this.viewsHolder = viewsHolder;

    }

    /**
     * 过程
     *
     * @param element 元素
     * @param holder 持有人
     * @param AdapterClass 适配器类
     */
    @Override
    public void process(Element element, EComponentHolder holder, JDefinedClass AdapterClass) {
        JMethod getViewMethod = AdapterClass.getMethod("getComponent", new AbstractJType[]{getCodeModel().INT, getClasses().COMPONENT, getClasses().COMPONENT_CONTAINER});
        JFieldRef position = ref("position");
        JFieldRef convertView = ref("convertComponent");
        JFieldRef parent = ref("parent");

        String viewHolderClassName = adapterClassName + "ViewHolder";
        JDefinedClass viewHolderClass = null;
        try {
            viewHolderClass = viewsHolder.getGeneratedClass()._class(JMod.PRIVATE | JMod.STATIC, viewHolderClassName);
        } catch (JClassAlreadyExistsException e) {
        }

        AbstractJClass Model = getJClass(modelClassName);

        JFieldRef inflater = ref("inflater");
        JFieldRef models = _this().ref("models");

        JBlock methodBody = getViewMethod.body();

        final JFieldRef contentViewId;
        final String listItemId;
        final String defLayoutId = viewsHolder.getDefLayoutId();

        // Read the Layout from the XML file
        org.w3c.dom.Element node = viewsHolder.getDomElementFromId(fieldName);
        if (node.hasAttribute("tools:listitem")) {
            String listItem = node.getAttribute("tools:listitem");
            String listItemIdOri = listItem.substring(listItem.lastIndexOf('/') + 2);
            listItemId = listItemIdOri.replace("layout:", "Layout_");
            contentViewId = environment.getRClass().getIdStaticRef(listItemId, environment);
            String newItemLayoutId = listItemId.replaceFirst("Layout_", "");
            viewsHolder.addLayout(newItemLayoutId);
            viewsHolder.setDefLayoutId(newItemLayoutId);
        } else {
            throw new IllegalStateException("This should not happens, Validations not working");
        }

        Map<String, IdInfoHolder> fields = new HashMap<>();
        Map<String, IdInfoHolder> methods = new HashMap<>();
        if (!modelClassName.equals(String.class.getCanonicalName())) {
            String className = modelClassName;
            if (className.endsWith(generationSuffix())) {
                className = className.substring(0, className.length() - 1);
            }
            viewsHolder.findFieldsAndMethods(className, fieldName, fields, methods, true, true, listItemId);
        }


        JMethod inflaterMethod = AdapterClass.getMethod("inflate", new AbstractJType[]{getCodeModel().INT, getClasses().COMPONENT, getClasses().COMPONENT_CONTAINER, getClasses().LAYOUT_SCATTER});
        JConditional conditional = inflaterMethod.body()._if(convertView.eq(_null()).cor(convertView.invoke("getTag").eq(_null()).cor(convertView.invoke("getTag")._instanceof(viewHolderClass).not())));
        conditional._then()._return(inflater.invoke("parse").arg(contentViewId).arg(parent).arg(false));
        conditional._else()._return(convertView);

        final JVar rootView = methodBody.decl(
            getClasses().COMPONENT,
            "rootView",
            _this().invoke("inflate").arg(position).arg(convertView).arg(parent).arg(inflater)
        );

        final JVar viewHolder = methodBody.decl(
            JMod.FINAL,
            viewHolderClass,
            "viewHolder"
        );

        conditional = methodBody._if(rootView.ne(convertView));
        final JBlock createViewBody = conditional._then();
        JBlock useConvertViewBody = conditional._else();
        createViewBody.assign(viewHolder, _new(viewHolderClass));

        // Find the Views by Id and fill the viewHolder
        List<String> fieldNames = new LinkedList<>();
        if (modelClassName.equals(String.class.getCanonicalName())) {
            String viewClass = viewsHolder.getClassNameFromId("Id_text");
            if (viewClass != null) {
                final String holderFieldName = "Id_text";

                AbstractJClass idNameClass = getJClass(viewClass);
                JFieldRef idRef = environment.getRClass().getIdStaticRef(holderFieldName, environment);
                JVar viewField = viewHolderClass.field(JMod.PUBLIC, idNameClass, holderFieldName + DeclexConstant.VIEW);

                IJExpression findViewById = rootView.invoke("findComponentById").arg(idRef);
                if (!idNameClass.equals(CanonicalNameConstants.COMPONENT)) {
                    findViewById = cast(idNameClass, findViewById);
                }

                createViewBody.assign(viewHolder.ref(viewField), findViewById);

                fieldNames.add(holderFieldName);
            }
        }

        for (Map.Entry<String, IdInfoHolder> entry : fields.entrySet()) {
            final String holderFieldName = fields.get(entry.getKey()).idName;

            AbstractJClass idNameClass = getJClass(fields.get(entry.getKey()).viewClass);
            JFieldRef idRef = environment.getRClass().getIdStaticRef(holderFieldName, environment);

            JVar viewField = viewHolderClass.field(JMod.PUBLIC, idNameClass, holderFieldName + DeclexConstant.VIEW);

            IJExpression findViewById = rootView.invoke("findComponentById").arg(idRef);
            if (!idNameClass.equals(CanonicalNameConstants.COMPONENT)) {
                findViewById = cast(idNameClass, findViewById);
            }

            createViewBody.assign(viewHolder.ref(viewField), findViewById);

            fieldNames.add(holderFieldName);
        }

        for (Map.Entry<String, IdInfoHolder> entry : methods.entrySet()) {
            final String holderFieldName = methods.get(entry.getKey()).idName;

            AbstractJClass idNameClass = getJClass(methods.get(entry.getKey()).viewClass);
            JFieldRef idRef = environment.getRClass().getIdStaticRef(holderFieldName, environment);

            JVar viewField = viewHolderClass.fields().get(holderFieldName + DeclexConstant.VIEW);
            if (viewField == null)
                viewField = viewHolderClass.field(JMod.PUBLIC, idNameClass, methods.get(entry.getKey()).idName + DeclexConstant.VIEW);

            IJExpression findViewById = rootView.invoke("findComponentById").arg(idRef);
            if (!idNameClass.equals(CanonicalNameConstants.COMPONENT)) {
                findViewById = cast(idNameClass, findViewById);
            }

            createViewBody.assign(viewHolder.ref(viewField), findViewById);

            fieldNames.add(holderFieldName);
        }

        JConditional ifConvertViewCorrectInstance = useConvertViewBody._if(convertView.invoke("getTag")._instanceof(viewHolderClass));
        ifConvertViewCorrectInstance._then().assign(viewHolder, cast(viewHolderClass, convertView.invoke("getTag")));
        ifConvertViewCorrectInstance._else().assign(viewHolder, _null());


        boolean castNeeded = false;
        if (!modelClassName.endsWith(generationSuffix())) {
            if (TypeUtils.isClassAnnotatedWith(modelClassName, UseModel.class, environment)) {
                modelClassName = TypeUtils.getGeneratedClassName(modelClassName, environment);
                castNeeded = true;
                Model = getJClass(modelClassName);
            }
        }

        //Listener to create the findViewById for every created view
        final JDefinedClass FinalViewHolderClass = viewHolderClass;
        viewsHolder.setCreateViewListener(new ICreateViewListener() {

            @Override
            public JFieldRef createView(String viewId, String viewName,
                                        AbstractJClass viewClass, JBlock declBlock) {

                if (!viewsHolder.layoutContainsId(viewId, listItemId)) return null;

                JVar viewField = FinalViewHolderClass.fields().get(viewName);
                if (viewField == null) {

                    AbstractJClass idNameClass = getJClass(viewsHolder.getClassNameFromId(viewId));
                    JFieldRef idRef = environment.getRClass().getIdStaticRef(viewId, environment);

                    viewField = FinalViewHolderClass.field(JMod.PUBLIC, idNameClass, viewName);
                    IJExpression findViewById = rootView.invoke("findComponentById").arg(idRef);
                    if (!idNameClass.equals(CanonicalNameConstants.COMPONENT)) {
                        findViewById = cast(idNameClass, findViewById);
                    }

                    createViewBody.assign(viewHolder.ref(viewField), findViewById);
                }

                return viewHolder.ref(viewField);
            }
        });

        //Get the model
        methodBody = methodBody._if(models.invoke("size").gt(position).cand(viewHolder.neNull()))._then();

        IJExpression modelAssigner = models.invoke("get").arg(position);
        if (castNeeded) modelAssigner = cast(Model, models.invoke("get").arg(position));
        JVar model = methodBody.decl(JMod.FINAL, Model, "model", modelAssigner);

        if (modelClassName.equals(String.class.getCanonicalName())) {
            String viewClass = viewsHolder.getClassNameFromId("Id_text");
            if (viewClass != null) {
                TypeElement stringElement = environment.getProcessingEnvironment().getElementUtils().getTypeElement(String.class.getCanonicalName());
                IdInfoHolder info = new IdInfoHolder("Id_text", stringElement, viewClass);
                JFieldRef view = viewHolder.ref(info.idName + DeclexConstant.VIEW);
                String newItemLayoutId = listItemId.replaceFirst("Layout_", "");
                handler.putAssignInBlock(info, methodBody, view, model, element, viewsHolder, null, newItemLayoutId);
            }
        }

        for (Map.Entry<String, IdInfoHolder> entry : fields.entrySet()) {
            IJExpression methodsCall = model;
            JBlock checkForNull = methodBody;

            String[] fieldSplit = entry.getKey().split("\\.");
            int index = 0;
            for (String fieldPart : fieldSplit)
                if (!fieldPart.equals("")) {
                    methodsCall = methodsCall.invoke(fieldToGetter(fieldPart));

                    if (index < fieldSplit.length - 1) {
                        checkForNull = checkForNull._if(methodsCall.ne(_null()))._then();
                    } else if (!fields.get(entry.getKey()).type.getKind().isPrimitive()) {
                        checkForNull = checkForNull._if(methodsCall.ne(_null()))._then();
                    }
                    index++;
                }

            IdInfoHolder info = fields.get(entry.getKey());
            JFieldRef view = viewHolder.ref(info.idName + DeclexConstant.VIEW);
            handler.putAssignInBlock(info, checkForNull, view, methodsCall, element, viewsHolder, null, listItemId
            );
        }

        for (Map.Entry<String, IdInfoHolder> entry : methods.entrySet()) {
            IJExpression methodsCall = model;
            JBlock checkForNull = methodBody;

            String[] methodSplit = entry.getKey().split("\\.");
            for (int i = 0; i < methodSplit.length - 1; i++)
                if (!methodSplit[i].equals("")) {
                    methodsCall = methodsCall.invoke(fieldToGetter(methodSplit[i]));
                    checkForNull = checkForNull._if(methodsCall.ne(_null()))._then();
                }
            methodsCall = methodsCall.invoke(methodSplit[methodSplit.length - 1]);

            IdInfoHolder info = methods.get(entry.getKey());
            JFieldRef view = viewHolder.ref(info.idName + DeclexConstant.VIEW);
            handler.putAssignInBlock(info, checkForNull, view, methodsCall, element, viewsHolder, null, listItemId);
        }

        viewsHolder.setInList();

        //Process Events
        Map<Class<?>, Object> listenerHolders = viewsHolder.holder().getPluginHolders();
        for (Object listenerHolderObject : listenerHolders.values()) {
            if (!(listenerHolderObject instanceof ViewListenerHolder)) continue;
            final ViewListenerHolder listenerHolder = (ViewListenerHolder) listenerHolderObject;

            for (String viewId : listenerHolder.getViewFieldNames()) {
                if (!viewsHolder.layoutContainsId(viewId)) continue;

                final JBlock eventsBlock = new JBlock();
                viewsHolder.createAndAssignView(viewId, new IWriteInBloc() {

                    @Override
                    public void writeInBlock(String viewName, AbstractJClass viewClass, JFieldRef view, JBlock block) {
                        JBlock ifNeNull = eventsBlock._if(viewHolder.ref(viewName).neNull())._then();
                        listenerHolder.createListener("viewHolder." + viewName, ifNeNull);
                    }
                });

                methodBody.add(eventsBlock);
            }
        }

        handler.callPopulateSupportMethod(fieldName, methodBody, viewHolder, fieldNames, element, viewsHolder);

        createViewBody.add(invoke(rootView, "setTag").arg(viewHolder));
        getViewMethod.body()._return(rootView);

        viewsHolder.resetInList();
        viewsHolder.setCreateViewListener(null);
        viewsHolder.setDefLayoutId(defLayoutId);
    }

    /**
     * 得到基本适配器
     *
     * @param element 元素
     * @return {@link AbstractJClass}
     */
    @Override
    public AbstractJClass getBaseAdapter(Element element) {
        return null;
    }
}

